// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//

#include <string>
#include <cantor/type/enum_builder.h>
#include <turbo/crypto/md5.h>

namespace cantor {

    turbo::Status EnumBuilder::is_name_conflict(const std::string &name) const {
        auto up_name = turbo::str_to_upper(name);
        return EnumBuilder::is_name_conflict_impl(up_name, name);
    }

    turbo::Status EnumBuilder::is_name_conflict_impl(const std::string &up_name, const std::string &name) const {
        KCHECK(!_final);
        auto nit = _name_to_value.find(up_name);
        if (nit != _name_to_value.end()) {
            return turbo::already_exists_error("already exists ", up_name, " origin ", name, ":", nit->second);
        }
        return turbo::OkStatus();
    }

    turbo::Status EnumBuilder::is_value_conflict(uint32_t value, bool &has_alias) const {
        KCHECK(!_final);
        auto it = _value_to_name.find(value);
        has_alias = false;
        if (it != _value_to_name.end() && !it->second.empty()) {
            if (!_allow_alias) {
                return turbo::already_exists_error("not allow alias ", it->second[0], " to ", it->first, ":", value);
            }
            has_alias = true;
        }
        return turbo::OkStatus();
    }

    turbo::Result<uint32_t> EnumBuilder::find_value(const std::string &name) const {
        auto up_name = turbo::str_to_upper(name);
        auto it = _name_to_value.find(up_name);
        if (it != _name_to_value.end()) {
            return it->second;
        }
        return turbo::not_found_error("not found ", up_name, " origin: ", name);
    }

    turbo::Status EnumBuilder::emplace(const std::string &name, uint32_t value) {
        KCHECK(!_final);
        if (!valid()) {
            return _status;
        }
        auto up_name = turbo::str_to_upper(name);

        _status = is_name_conflict_impl(up_name, name);
        TURBO_RETURN_NOT_OK(_status);
        bool has_alias = false;
        _status = is_value_conflict(value, has_alias);
        TURBO_RETURN_NOT_OK(_status);
        _has_alias |= has_alias;
        _current_value = value;
        _value_to_name[_current_value].push_back(up_name);
        _name_to_value[up_name] = _current_value;
        _max_value = std::max(_max_value, _current_value);
        ++_current_value;
        return turbo::OkStatus();
    }

    turbo::Status EnumBuilder::emplace(const std::string &name) {
        return emplace(name, _current_value);
    }

    turbo::Status EnumBuilder::final_status() {
       KCHECK(!_final);
        _final = true;
        if (!_status.ok()) {
            return _status;
        }
        if (_uri.empty()) {
            return turbo::data_loss_error("missing uri");
        }
        if (_name_to_value.empty()) {
            return turbo::data_loss_error("Enum need at least one field");
        }
        for (auto &it : _value_to_name) {
            if (it.second.empty()) {
                return turbo::data_loss_error("missing value ", it.first);
            }
        }
        return turbo::OkStatus();
    }


    std::string EnumData::to_string() const {
        if (!status.ok()) {
            return status.to_string();
        }
        std::stringstream ss;

        std::vector<uint32_t> values;
        for (auto &it: value_to_name) {
            values.push_back(it.first);
        }
        std::sort(values.begin(), values.end());
        ss << "Enum " << uri << " {\n";
        for (auto &it: values) {
            auto ele = value_to_name.find(it);
            for (auto &eit: ele->second) {
                ss << "\t" << eit << " = " << it << ", \n";
            }
        }
        ss << "\t" << "ENUM_MAX_VALUE = " << max_value << "\n";
        ss << "}\n";

        return ss.str();
    }

    void EnumData::check_sum() {
        turbo::MD5 md5;
        auto base_attr = turbo::str_cat("allow_alias:", allow_alias, "has_alias: ", has_alias);
        md5.update(base_attr);
        auto ex = to_string();
        md5.update(ex);
        auto f = md5.final();
        fingerprint = turbo::make_uint128(f.high(), f.low());
    }
    bool EnumData::operator==(const EnumData& other) const {
        if (!status.ok() || !other.status.ok()) {
            return false;
        }
        return fingerprint == other.fingerprint;
    }
    std::string EnumBuilder::to_string() const {
        if (!_status.ok()) {
            return _status.to_string();
        }
        std::stringstream ss;
        if (_final) {
            return "already built";
        }

        std::vector<uint32_t> values;
        for (auto &it: _value_to_name) {
            values.push_back(it.first);
        }
        std::sort(values.begin(), values.end());
        ss << "Enum " << _uri << " {\n";
        for (auto &it: values) {
            auto ele = _value_to_name.find(it);
            for (auto &eit: ele->second) {
                ss << "\t" << eit << " = " << it << ", \n";
            }
        }
        ss << "\t" << "ENUM_MAX_VALUE = " << _max_value << "\n";
        ss << "}\n";

        return ss.str();
    }

    EnumData EnumBuilder::build() && {
        KCHECK(!_final);
        EnumData data;
        _status = final_status();
        data.status = _status;
        if (!data.status.ok()) {
            return data;
        }
        data.uri = std::move(_uri);
        data.name_to_value = std::move(_name_to_value);
        data.max_value = _max_value;
        data.value_to_name = std::move(_value_to_name);
        data.has_alias = _has_alias;
        data.allow_alias = _allow_alias;
        data.check_sum();
        return data;
    }
} // namespace cantor
